/*
** Copyright (C) 2015-2016  Michael Roland <mi.roland@fh.hagenberg.at>
**                          FH OÖ Forschungs & Entwicklungs GmbH
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program.  If not, see <http://www.gnu.org/licenses/>.
**
*/
package org.simalliance.openmobileapi.service.terminals.exploit;

import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.os.Process;
import android.content.pm.PackageManager;
import android.content.pm.PermissionInfo;
import android.nfc.NfcAdapter;
import android.os.Environment;
import android.os.IBinder;
import android.provider.Settings;
import android.util.Log;

import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ContextInfo extends DumpableInfo {
    private static final String LOG_TAG = ContextInfo.class.getSimpleName();

    public final int USER_ID;
    public final String USER_NAME;
    public final int PROCESS_ID;
    public final String PROCESS_NAME;
    public final int THREAD_ID;
    public final String PACKAGE_NAME;
    public final List<String> GRANTED_PERMISSIONS;

    public final boolean CAN_WRITE_EXTERNAL_STORAGE;
    public final boolean WRITE_SECURE_SETTINGS;
    public final String WRITE_SECURE_SETTINGS_EXCEPTION;
    public final boolean WRITE_TO_SETTINGS_SECURE;
    public final String WRITE_TO_SETTINGS_SECURE_EXCEPTION;
    public final boolean MODIFY_PHONE_STATE;
    public final String MODIFY_PHONE_STATE_EXCEPTION;
    
    public ContextInfo(Context context) {
        // process and application context information
        USER_ID = Process.myUid();
        USER_NAME = context.getPackageManager().getNameForUid(USER_ID);
        PROCESS_ID = Process.myPid();
        PROCESS_NAME = getProcessNameFromPid(context, PROCESS_ID);
        THREAD_ID = Process.myTid();
        PACKAGE_NAME = context.getPackageName();

        // collect list of possible permissions
        final ArrayList<String> permsToTest = new ArrayList();

        // add known system permissions from android.Manifest.permission class
        Field[] fieldsManifestPerm = android.Manifest.permission.class.getDeclaredFields();
        for (Field field : fieldsManifestPerm) {
            if (field.getType() == String.class) {
                try {
                    field.setAccessible(true);
                    permsToTest.add((String)field.get(null));
                } catch (Exception e) {
                }
            }
        }
        
        // add permissions requested or declared in the application package
        try {
            PackageInfo packageInfo = context.getPackageManager().getPackageInfo(PACKAGE_NAME, PackageManager.GET_PERMISSIONS);
            if (packageInfo != null) {
                if (packageInfo.requestedPermissions != null) {
                    for (String perm : packageInfo.requestedPermissions) {
                        if (!permsToTest.contains(perm)) {
                            permsToTest.add(perm);
                        }
                    }
                }
                if (packageInfo.permissions != null) {
                    for (PermissionInfo permInfo : packageInfo.permissions) {
                        if (permInfo != null) {
                            if (!permsToTest.contains(permInfo.name)) {
                                permsToTest.add(permInfo.name);
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
        }

        // add other interesting permissions
        final String[] additionalPerms = new String[] {
            "android.permission.BIND_NFC_SERVICE",
            "com.android.nfc.permission.NFCEE_ADMIN",
        };
        for (String perm : additionalPerms) {
            if (!permsToTest.contains(perm)) {
                permsToTest.add(perm);
            }
        }

        // check permissions for our process
        ArrayList<String> grantedPermissions = new ArrayList<String>();
        for (String perm : permsToTest) {
            try {
                int res = context.checkPermission(perm, PROCESS_ID, USER_ID);
                if (res == PackageManager.PERMISSION_GRANTED) {
                    grantedPermissions.add(perm);
                }
            } catch (Exception e) {
            }
        }
        GRANTED_PERMISSIONS = Collections.unmodifiableList(grantedPermissions);

        boolean canWriteExternalStorage = false;
        try {
            File extFile = new File(Environment.getExternalStorageDirectory(), "q5xQc4Zo");
            FileOutputStream fileOut = new FileOutputStream(extFile);
            fileOut.write(new byte[] { '1' });
            fileOut.flush();
            fileOut.close();
            canWriteExternalStorage = true;
        } catch (Exception e) {
        }
        CAN_WRITE_EXTERNAL_STORAGE = canWriteExternalStorage;
        
        boolean writeToSettingsSecure = false;
        String writeToSettingsSecureException = null;
        try {
            writeToSettingsSecure = Settings.Secure.putInt(context.getContentResolver(), Settings.Secure.INSTALL_NON_MARKET_APPS, 1);
        } catch (Throwable e) {
            Log.e(LOG_TAG, "Settings.Secure.putInt failed with exception", e);
            while ((e.getCause() != null) && (e.getCause() != e)) {
                e = e.getCause();
            }
            writeToSettingsSecureException = e.toString();
        }
        WRITE_TO_SETTINGS_SECURE = writeToSettingsSecure;
        WRITE_TO_SETTINGS_SECURE_EXCEPTION = writeToSettingsSecureException;
        
        boolean writeSecureSettings = false;
        String writeSecureSettingsException = null;
        try {
            Class classNfcAdapter = NfcAdapter.class;
            NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(context);
            Method methodEnableNfc = classNfcAdapter.getMethod("enable");
            methodEnableNfc.invoke(nfcAdapter);
            writeSecureSettings = true;
        } catch (Throwable e) {
            Log.e(LOG_TAG, "NfcAdapter.enable failed with exception", e);
            while ((e.getCause() != null) && (e.getCause() != e)) {
                e = e.getCause();
            }
            writeSecureSettingsException = e.toString();
        }
        WRITE_SECURE_SETTINGS = writeSecureSettings;
        WRITE_SECURE_SETTINGS_EXCEPTION = writeSecureSettingsException;
        
        boolean modifyPhoneState = false;
        String modifyPhoneStateException = null;
        try {
            ClassLoader classLoader = context.getClassLoader();
            Class classServiceManager = classLoader.loadClass("android.os.ServiceManager");
            Method methodGetService = classServiceManager.getMethod("getService", String.class);
            IBinder service = (IBinder)methodGetService.invoke(null, "phone");
            Class classITelephony = classLoader.loadClass("com.android.internal.telephony.ITelephony");
            Class classITelephonyStub = null;
            for (Class clazz : classITelephony.getClasses()) {
                if ("Stub".equals(clazz.getSimpleName())) {
                    classITelephonyStub = clazz;
                    break;
                }
            }
            Method methodAsInterface = classITelephonyStub.getMethod("asInterface", IBinder.class);
            Object objITelephony = methodAsInterface.invoke(null, service);
            Method methodEnableDataConnectivity = classITelephony.getMethod("enableDataConnectivity");
            methodEnableDataConnectivity.invoke(objITelephony);
            modifyPhoneState = true;
        } catch (Throwable e) {
            Log.e(LOG_TAG, "testModifyPhoneState failed with exception", e);
            while ((e.getCause() != null) && (e.getCause() != e)) {
                e = e.getCause();
            }
            modifyPhoneStateException = e.toString();
        }
        MODIFY_PHONE_STATE = modifyPhoneState;
        MODIFY_PHONE_STATE_EXCEPTION = modifyPhoneStateException;
    }
    
    private String getProcessNameFromPid(Context context, int pid) {
        try {
            List<ActivityManager.RunningAppProcessInfo> appProcessInfos =
                    ((ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE)).getRunningAppProcesses();
            for (ActivityManager.RunningAppProcessInfo appProcessInfo : appProcessInfos) {
                if (appProcessInfo.pid == pid) {
                    return appProcessInfo.processName;
                }
            }
        } catch (Exception e) {
        }
        return "";
    }
    
    public void dump(PrintStream out) {
        out.println("#######################################################################");
        out.println("# CURRENT CONTEXT");
        out.println("#######################################################################");
        out.println("Package name: " + PACKAGE_NAME);
        out.println("User ID: " + USER_ID);
        out.println("User name: " + USER_NAME);
        out.println("Process ID: " + PROCESS_ID);
        out.println("Process name: " + PROCESS_NAME);
        out.println("Thread ID: " + THREAD_ID);
        out.println("Granted permissions:");
        for (String s : GRANTED_PERMISSIONS) {
            out.println("    " + s);
        }
        out.println("Can write external storage? " + CAN_WRITE_EXTERNAL_STORAGE);
        out.println("Can access API protected with WRITE_SECURE_SETTINGS permission? " + WRITE_SECURE_SETTINGS);
        if (!WRITE_SECURE_SETTINGS) {
            out.println("    " + WRITE_SECURE_SETTINGS_EXCEPTION);
        }
        out.println("Can access write to Settings.Secure? " + WRITE_TO_SETTINGS_SECURE);
        if (!WRITE_TO_SETTINGS_SECURE) {
            out.println("    " + WRITE_TO_SETTINGS_SECURE_EXCEPTION);
        }
        out.println("Can access API protected with MODIFY_PHONE_STATE permission? " + MODIFY_PHONE_STATE);
        if (!MODIFY_PHONE_STATE) {
            out.println("    " + MODIFY_PHONE_STATE_EXCEPTION);
        }
        out.println("#######################################################################");
        out.println();
    }
}
